#include "ClangFunction.h"
#include "DefineLibReflection.h"
#include "EString.h"
#include <iostream>

#include <regex>
#include <clang-c/Index.h>

#include <DefineCommon.h>

// #define SPDLOG_ACTIVE_LEVEL SPDLOG_LEVEL_DEBUG

EString ClangFunction::CXStringToEString(const CXString& s)
{
    // ELOG_DEBUG("cxstring=%s",clang_getCString(s));
	EString str(clang_getCString(s));
    // ELOG_DEBUG("EString=%s",str.data());

	clang_disposeString(s);

	return str;
}

EString ClangFunction::getCursorFullName(CXCursor cursor)
{
	EString name;
	while (clang_isDeclaration(clang_getCursorKind(cursor)) != 0)
	{
		EString cur = CXStringToEString(clang_getCursorSpelling(cursor));
		if (name.isEmpty())
		{
			name = cur;
		}
		else
		{
			name = cur + "::" + name;
		}
		cursor = clang_getCursorSemanticParent(cursor);
	}

	return name;
}

EString ClangFunction::getCXTypeName(const CXType& type)
{
	//TODO: unfortunately, this isn't good enough. It only works as long as the
	// type is fully qualified.
	return CXStringToEString(clang_getTypeSpelling(type));
}

EString ClangFunction::getCursorFile(const CXCursor& cursor)
{
	CXSourceLocation location = clang_getCursorLocation(cursor);
	CXFile file;
	clang_getSpellingLocation(location, &file, nullptr, nullptr, nullptr);
	return CXStringToEString(clang_getFileName(file));
}


bool ClangFunction::IsRecursivelyPublic(CXCursor cursor)
{
	while (clang_isDeclaration(clang_getCursorKind(cursor)) != 0)
	{
		auto access = clang_getCXXAccessSpecifier(cursor);
		if (access == CX_CXXPrivate || access == CX_CXXProtected)
		{
			return false;
		}

		if (clang_getCursorLinkage(cursor) == CXLinkage_Internal)
		{
			return false;
		}

		if (clang_getCursorKind(cursor) == CXCursor_Namespace
				&& CXStringToEString(clang_getCursorSpelling(cursor)).isEmpty())
		{
			// Anonymous namespace.
			return false;
		}

		cursor = clang_getCursorSemanticParent(cursor);
	}

	return true;
}

void ClangFunction::fileToCollection(CppInfo::CppFilePtr filePtr, CppInfo::CppCollectionPtr collPtr )
{
    //TODO, the problem is not resolved: more than one -I parameters is not useful to clang.
    collPtr->needClassNameList = filePtr->classNameList;

    unsigned int countArg = 0;
    const char* argv[50] = {};
    argv[0] = {"-std=c++14"};
    ELOG_DEBUG("argv[%d]=%s", countArg, argv[countArg]);
    countArg++;

    argv[countArg] = {"-x"};
    ELOG_DEBUG("argv[%d]=%s", countArg, argv[countArg]);
    countArg++;

    argv[countArg] = {"c++"};
    ELOG_DEBUG("argv[%d]=%s", countArg, argv[countArg]);
    countArg++;

    if(countArg < 50)
    {
        argv[countArg] = {"-Dlinux"};
        ELOG_DEBUG("argv[%d]=%s", countArg, argv[countArg]);
        countArg++;
    }

    for(int i=0; i < (unsigned int)filePtr->incFilePathList.size() && countArg < 50; i=i+1)
    {
        EString incStr = EString("-I")+filePtr->incFilePathList[i];

        char dest[150];
        strcpy(dest, incStr.data());
        argv[countArg] = dest;
        ELOG_DEBUG("argv[%d]=%s", countArg, argv[countArg]);

        countArg++;
    }
    
    int argc=countArg;

    CXIndex index = clang_createIndex(0, 0);
    
    //CXTranslationUnit unit = Parse(index, file, argc, argv);
    CXTranslationUnit unit = clang_parseTranslationUnit(
				index,
				filePtr->fileName.data(), 
                argv, 
                argc,
				nullptr, 
                0,
				CXTranslationUnit_None);

    // LOG_DEBUG("filePtr->fileName={}", filePtr->fileName.toLocal8Bit().data());

	if (unit == nullptr)
	{
        ELOG_ERROR("Unable to parse translation unit. Quitting.")
        return ;
	}
	
    unsigned diagnostics = clang_getNumDiagnostics(unit);

	if (diagnostics != 0)
	{
        ELOG_DEBUG("> Diagnostics:");

		for (int i = 0; i != diagnostics; ++i)
		{
			CXDiagnostic diag = clang_getDiagnostic(unit, i);
            CXString strDiag = clang_formatDiagnostic( diag, clang_defaultDiagnosticDisplayOptions());
            ELOG_DEBUG(">>>%s", (const char*)strDiag.data);
		}
	}

    CXCursor cursor = clang_getTranslationUnitCursor(unit);

    // ELOG_DEBUG("comment=%s", CXStringToEString(clang_Cursor_getRawCommentText(cursor)).data());
    // std::string infor;
	unsigned visitReturn = clang_visitChildren(cursor, ClangFunction::callBackFile, collPtr.get());

    for(EMap<EString, CppInfo::ClassPtr>::iterator it = collPtr->mapClassPtr.begin();
            it != collPtr->mapClassPtr.end();
            it++)
    {
        CppInfo::ClassPtr c = it->second;
        EStringList pnList;
        fillClassParentNameList(c, collPtr, pnList);
        c->parentNameList = pnList;
        // ELOG_DEBUG("%s", c->toString().toStdString())
    }

    ELOG_DEBUG("Last class name=%s", collPtr->listClassPtr[collPtr->listClassPtr.size()-1]->name.data());
    if(collPtr->needClassNameList.size() == 0)
    {
        collPtr->needClassNameList.push_back(
                collPtr->listClassPtr[collPtr->listClassPtr.size()-1]->name
                );
    }
    // std::cout<<"visitReturn="<<visitReturn<<"\n";
    // std::cout<<"Enum break="<<CXChildVisit_Break<<"\n";
    // std::cout<<"Enum Continue="<<CXChildVisit_Continue<<"\n";
    // std::cout<<"Enum Recurse="<<CXChildVisit_Recurse<<"\n";

    //remove classes that name != filePtr->classNameList
    // for(QMap<EString, CppInfo::ClassPtr>::iterator it = collPtr->mapClassPtr.begin();
            // it != collPtr->mapClassPtr.begin();
            // it++)
    // {
        // EString name = it.key();
        // bool ifClassNameContains = false;
        // for(int i = 0 ; i < filePtr->classNameList.size(); i++)
        // {
           // if(filePtr->classNameList[i].contains(name))
           // {
               // ifClassNameContains = true;
               // break;
           // }
        // }
    // }


}

CppInfo::ClassPtr ClangFunction::cursorToClassPtr(CXCursor cursor)
{
    CppInfo::ClassPtr newClass(new CppInfo::Class);
    newClass->name = getCursorFullName(cursor);

    newClass->description += CXStringToEString(clang_Cursor_getRawCommentText(cursor));
    // ELOG_DEBUG("newClass->name=%s", newClass->name.data());

    newClass->ifPureVirtual = clang_CXXRecord_isAbstract(cursor);
	clang_visitChildren(cursor, ClangFunction::callBackClass, newClass.get());
    return newClass;
}

CppInfo::FunctionPtr ClangFunction::cursorToFunctionPtr(CXCursor cursor)
{
    CppInfo::FunctionPtr funPtr(new CppInfo::Function);

	CXType type = clang_getCursorType(cursor);

    //parser::GetFile(cursor), parser::GetFullName(cursor));
    EString fileName = ClangFunction::getCursorFile(cursor);
    EString fullName = ClangFunction::getCursorFullName(cursor);
	//f.Name = parser::Convert(clang_getCursorSpelling(cursor));
    EString functionName = ClangFunction::CXStringToEString(clang_getCursorSpelling(cursor));
    funPtr->name = functionName;


    funPtr->ifPureVirtual = clang_CXXMethod_isPureVirtual(cursor);
    

	int num_args = clang_Cursor_getNumArguments(cursor);

	for (int i = 0; i < num_args; ++i)
	{
        CppInfo::VariablePtr variPtr(new CppInfo::Variable);
        funPtr->parameterList.push_back(variPtr);

		CXCursor arg_cursor = clang_Cursor_getArgument(cursor, i);
        EString argName = ClangFunction::CXStringToEString(clang_getCursorSpelling(arg_cursor));

        variPtr->name = argName;

        if(argName.isEmpty())
        {
            argName = "nameless";
        }

		CXType arg_type = clang_getArgType(type, i);
        EString argType = ClangFunction::getCXTypeName(arg_type);

        variPtr->typeName = argType;
	}

    EString returnType = ClangFunction::getCXTypeName(clang_getResultType(type));

    funPtr->returnType = returnType;

	return funPtr;
}

CppInfo::VariablePtr ClangFunction::cursorToVariablePtr(CXCursor cursor)
{
    CppInfo::VariablePtr varPtr(new CppInfo::Variable);
	varPtr->name = ClangFunction::CXStringToEString(clang_getCursorSpelling(cursor));
    varPtr->typeName = ClangFunction::getCXTypeName(clang_getCursorType(cursor));
    return varPtr;
}

CXChildVisitResult ClangFunction::callBackFile(CXCursor cursor, CXCursor parent, CXClientData client_data)
{

    CppInfo::CppCollection* cppCollPtr = (CppInfo::CppCollection*)(client_data);
    CXCursorKind kind = clang_getCursorKind(cursor);

    if(kind == CXCursor_EnumDecl)
    {
        // std::cout<<"Enum"<<"\n";
    }
    else if(kind == CXCursor_ClassDecl || kind == CXCursor_StructDecl)
    {

        CppInfo::ClassPtr newClass = ClangFunction::cursorToClassPtr(cursor);

        // EString commentStr = CXStringToEString(clang_Cursor_getRawCommentText(cursor));
        EString commentStr = CXStringToEString(clang_Cursor_getBriefCommentText(cursor));
        if(commentStr.isEmpty() == false)
        {
            newClass->description = commentStr;
            // ELOG_DEBUG("comment=\n%s\nclass name=%s",commentStr.data(), newClass->name.data());
        }
        
        
        // if(cppCollPtr->needClassNameList.contains(newClass->name) )
        // if(EString::EStringListContains(
                    // cppCollPtr->needClassNameList,
                    // newClass->name))
        if(newClass->name.isEmpty() == false)
        {
            // ELOG_DEBUG("newClass name=%s", newClass->name.data());
            cppCollPtr->mapClassPtr[newClass->name]=newClass;
            cppCollPtr->listClassPtr.push_back(newClass);
        }
    }
    else if(kind == CXCursor_FunctionDecl)
    {
        // std::cout<<"Function"<<"\n";
    }
    else
    {
    }

	return CXChildVisit_Recurse;
}

CXChildVisitResult ClangFunction::callBackClass(CXCursor cursor, CXCursor parent, CXClientData client_data)
{
    CppInfo::Class* newClass = (CppInfo::Class*)(client_data);

	// if (clang_getCXXAccessSpecifier(cursor) == CX_CXXPublic)
    CXCursorKind cursorKind = clang_getCursorKind(cursor);
    if(cursorKind == CXCursor_CXXMethod)
    {
		if (clang_getCXXAccessSpecifier(cursor) == CX_CXXPublic)
        {
            CppInfo::FunctionPtr funPtr = cursorToFunctionPtr(cursor);
            newClass->functionList.push_back(funPtr);
        }
        // clang_CXXMethod_isStatic
        // std::cout<<newClass->name.toStdString()<<" CXCursor_Method"<<"\n";
    }
    else if(cursorKind == CXCursor_Constructor)
    {
		if (clang_getCXXAccessSpecifier(cursor) == CX_CXXPublic)
        {
            CppInfo::FunctionPtr funPtr = cursorToFunctionPtr(cursor);
            newClass->constructorList.push_back(funPtr);
        }
    }
    else if(cursorKind == CXCursor_Destructor)
    {
		if (clang_getCXXAccessSpecifier(cursor) == CX_CXXPublic)
        {
            CppInfo::FunctionPtr funPtr = cursorToFunctionPtr(cursor);
            newClass->destructorList.push_back(funPtr);
        }
    }
    else if(cursorKind == CXCursor_FieldDecl)
    {
        // std::cout<<newClass->name.toStdString()<<" CXCursor_FieldDecl"<<"\n";
        CppInfo::VariablePtr varPtr = cursorToVariablePtr(cursor);
        newClass->variableList.push_back(varPtr);
    }
    else if(cursorKind == CXCursor_VarDecl)
    {
        // std::cout<<newClass->name.toStdString()<<" CXCursor_VarDecl"<<"\n";
        CppInfo::VariablePtr varPtr = cursorToVariablePtr(cursor);
        newClass->variableList.push_back(varPtr);
    }
    else if(cursorKind == CXCursor_CXXBaseSpecifier)
    {
        EString baseClassName = ClangFunction::getCXTypeName(clang_getCursorType(cursor));
        newClass->parentNameList.push_back( baseClassName );
        // std::cout<<"callBackClass, cursorKind == CXCursor_CXXBaseSpecifier type="<<type.toStdString()<<"\n";
    }
    else
    {
    }

		// switch (clang_getCursorKind(cursor))
		// {
		// case CXCursor_CXXMethod:
		// case CXCursor_FieldDecl:
			// // c->Fields.push_back(GetFieldFromCursor(cursor));
			// break;
		// case CXCursor_VarDecl:
			// // c->StaticFields.push_back(GetFieldFromCursor(cursor));
			// break;
		// default:
			// break;
		// }
	return CXChildVisit_Continue;
}

void ClangFunction::fillClassParentNameList(CppInfo::ClassPtr classPtr, CppInfo::CppCollectionPtr coll, EStringList& pnList)
{
    for(int i = 0 ; i < classPtr->parentNameList.size(); i++)
    {
        EString pn = classPtr->parentNameList[i];
        pnList.push_back(pn);
        // if(coll->mapClassPtr.containj(pn))
        if(coll->mapClassPtr.find(pn) != coll->mapClassPtr.end())
        {
            CppInfo::ClassPtr parent = coll->mapClassPtr[pn];
            fillClassParentNameList(parent, coll, pnList);
        }
    }
}
